﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.ApplicationModel.Resources.Core;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using System.Net.Http;
using Windows.Data.Json;
using Windows.ApplicationModel;
using Windows.Storage.Streams;
using System.Threading.Tasks;
using Windows.Storage;
using Bing.Maps;

namespace BingMapsGeolocationTemplate.Data
{
    [Windows.Foundation.Metadata.WebHostHidden]
    public abstract class PlacesDataCommon : BingMapsGeolocationTemplate.Common.BindableBase
    {
        internal  static Uri _baseUri = new Uri("ms-appx:///");

        public PlacesDataCommon(String uniqueId, String title, String subtitle, String imagePath)
        {
            this._uniqueId = uniqueId;
            this._title = title;
            this._subtitle = subtitle;
            this._imagePath = imagePath;
        }

        private string _uniqueId = string.Empty;
        public string UniqueId
        {
            get { return this._uniqueId; }
            set { this.SetProperty(ref this._uniqueId, value); }
        }

        private string _title = string.Empty;
        public string Title
        {
            get { return this._title; }
            set { this.SetProperty(ref this._title, value); }
        }

        private string _subtitle = string.Empty;
        public string Subtitle
        {
            get { return this._subtitle; }
            set { this.SetProperty(ref this._subtitle, value); }
        }

        private ImageSource _image = null;
        private String _imagePath = null;

        public Uri ImagePath
        {
            get
            {
                return new Uri(PlacesDataCommon._baseUri, this._imagePath);
            }
        }

        public ImageSource Image
        {
            get
            {
                if (this._image == null && this._imagePath != null)
                {
                    this._image = new BitmapImage(new Uri(PlacesDataCommon._baseUri, this._imagePath));
                }
                return this._image;
            }

            set
            {
                this._imagePath = null;
                this.SetProperty(ref this._image, value);
            }
        }

        public void SetImage(String path)
        {
            this._image = null;
            this._imagePath = path;
            this.OnPropertyChanged("Image");
        }

        public string GetImageUri()
        {
            return _imagePath;
        }
    }

    public class PlacesDataItem : PlacesDataCommon
    {
        public PlacesDataItem()
            : base(String.Empty, String.Empty, String.Empty, String.Empty)
        {
        }

        public PlacesDataItem(String uniqueId, String title, String shortTitle, String imagePath, String descricao, PlacesDataGroup group, double longitude, double latitude)
            : base(uniqueId, title, shortTitle, imagePath)
        {
            this._descricao = descricao;
            this._group = group;
            this._latitude = latitude;
            this._longitude = longitude;

        }

        private double _latitude;

        public double Latitude
        {
            get { return this._latitude; }
            set { this.SetProperty(ref this._latitude, value);  }
        }

        private double _longitude;

        public double Longitude
        {
            get { return this._longitude; }
            set { this.SetProperty(ref this._longitude, value); }
        }
        

        private string _descricao = string.Empty;
        public string Descricao
        {
            get { return this._descricao; }
            set { this.SetProperty(ref this._descricao, value); }
        }

        private PlacesDataGroup _group;
        public PlacesDataGroup Group
        {
            get { return this._group; }
            set { this.SetProperty(ref this._group, value); }
        }

        private ImageSource _tileImage;
        private string _tileImagePath;

        public Uri TileImagePath
        {
            get
            {
                return new Uri(PlacesDataCommon._baseUri, this._tileImagePath);
            }
        }

        public ImageSource TileImage
        {
            get
            {
                if (this._tileImage == null && this._tileImagePath != null)
                {
                    this._tileImage = new BitmapImage(new Uri(PlacesDataCommon._baseUri, this._tileImagePath));
                }
                return this._tileImage;
            }
            set
            {
                this._tileImagePath = null;
                this.SetProperty(ref this._tileImage, value);
            }
        }

        public void SetTileImage(String path)
        {
            this._tileImage = null;
            this._tileImagePath = path;
            this.OnPropertyChanged("TileImage");
        }
    }

    /// <summary>
    /// Recipe group data model.
    /// </summary>
    public class PlacesDataGroup : PlacesDataCommon
    {
        public PlacesDataGroup()
            : base(String.Empty, String.Empty, String.Empty, String.Empty)
        {
        }

        public PlacesDataGroup(String uniqueId, String title, String shortTitle, String imagePath, String description)
            : base(uniqueId, title, shortTitle, imagePath)
        {
        }

        private ObservableCollection<PlacesDataItem> _items = new ObservableCollection<PlacesDataItem>();
        public ObservableCollection<PlacesDataItem> Items
        {
            get { return this._items; }
        }

        public IEnumerable<PlacesDataItem> TopItems
        {
            // Provides a subset of the full items collection to bind to from a GroupedItemsPage
            // for two reasons: GridView will not virtualize large items collections, and it
            // improves the user experience when browsing through groups with large numbers of
            // items.
            //
            // A maximum of 12 items are displayed because it results in filled grid columns
            // whether there are 1, 2, 3, 4, or 6 rows displayed
            get { return this._items.Take(12); }
        }

        private string _description = string.Empty;
        public string Description
        {
            get { return this._description; }
            set { this.SetProperty(ref this._description, value); }
        }

        private ImageSource _groupImage;
        private string _groupImagePath;

        public ImageSource GroupImage
        {
            get
            {
                if (this._groupImage == null && this._groupImagePath != null)
                {
                    this._groupImage = new BitmapImage(new Uri(PlacesDataCommon._baseUri, this._groupImagePath));
                }
                return this._groupImage;
            }
            set
            {
                this._groupImagePath = null;
                this.SetProperty(ref this._groupImage, value);
            }
        }

        public int RecipesCount
        {
            get
            {
                return this.Items.Count;
            }
        }

        public void SetGroupImage(String path)
        {
            this._groupImage = null;
            this._groupImagePath = path;
            this.OnPropertyChanged("GroupImage");
        }
    }

    /// <summary>
    /// Creates a collection of groups and items.
    /// </summary>
    public sealed class PlacesDataSource
    {
        //public event EventHandler RecipesLoaded;

        private static PlacesDataSource _placesDataSource = new PlacesDataSource();

        private ObservableCollection<PlacesDataGroup> _allGroups = new ObservableCollection<PlacesDataGroup>();
        public ObservableCollection<PlacesDataGroup> AllGroups
        {
            get { return this._allGroups; }
        }

        public static IEnumerable<PlacesDataGroup> GetGroups(string uniqueId)
        {
            if (!uniqueId.Equals("AllGroups")) throw new ArgumentException("Only 'AllGroups' is supported as a collection of groups");

            return _placesDataSource.AllGroups;
        }

        public static PlacesDataGroup GetGroup(string uniqueId)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _placesDataSource.AllGroups.Where((group) => group.UniqueId.Equals(uniqueId));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        public static PlacesDataItem GetItem(string uniqueId)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _placesDataSource.AllGroups.SelectMany(group => group.Items).Where((item) => item.UniqueId.Equals(uniqueId));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        public static async Task LoadLocalDataAsync()
        {
            // Retrieve recipe data from Recipes.txt
            var file = await Package.Current.InstalledLocation.GetFileAsync("Data\\Places.txt");
            var result = await FileIO.ReadTextAsync(file);

            // Parse the JSON recipe data
            var drinks = JsonArray.Parse(result);

            // Convert the JSON objects into RecipeDataItems and RecipeDataGroups
            CreatePlacesAndPlaceGroups(drinks);
        }

        private static void CreatePlacesAndPlaceGroups(JsonArray array)
        {
            foreach (var item in array)
            {
                var obj = item.GetObject();
                PlacesDataItem place = new PlacesDataItem();
                PlacesDataGroup group = null;

                foreach (var key in obj.Keys)
                {
                    IJsonValue val;
                    if (!obj.TryGetValue(key, out val))
                        continue;

                    switch (key)
                    {
                        case "key":
                            place.UniqueId = val.GetNumber().ToString();
                            break;
                        case "title":
                            place.Title = val.GetString();
                            break;
                        case "latitude":
                            place.Latitude = val.GetNumber();
                            break;
                        case "longitude":
                            place.Longitude = val.GetNumber();
                            break;
                        case "subtitle":
                            place.Subtitle = val.GetString();
                            break;
                        case "descricao":
                            place.Descricao = val.GetString();
                            break;
                        case "backgroundImage":
                            place.SetImage(val.GetString());
                            break;
                        case "tileImage":
                            place.SetTileImage(val.GetString());
                            break;
                        case "group":
                            var placeGroup = val.GetObject();

                            IJsonValue groupKey;
                            if (!placeGroup.TryGetValue("key", out groupKey))
                                continue;

                            group = _placesDataSource.AllGroups.FirstOrDefault(c => c.UniqueId.Equals(groupKey.GetString()));

                            if (group == null)
                                group = CreatePlaceGroup(placeGroup);

                            place.Group = group;
                            break;
                    }
                }

                if (group != null)
                    group.Items.Add(place);
            }
        }

        private static PlacesDataGroup CreatePlaceGroup(JsonObject obj)
        {
            PlacesDataGroup group = new PlacesDataGroup();

            foreach (var key in obj.Keys)
            {
                IJsonValue val;
                if (!obj.TryGetValue(key, out val))
                    continue;

                switch (key)
                {
                    case "key":
                        group.UniqueId = val.GetString();
                        break;
                    case "title":
                        group.Title = val.GetString();
                        break;
                    case "subtitle":
                        group.Subtitle = val.GetString();
                        break;
                    case "description":
                        group.Description = val.GetString();
                        break;
                    case "backgroundImage":
                        group.SetImage(val.GetString());
                        break;
                    case "groupImage":
                        group.SetGroupImage(val.GetString());
                        break;
                }
            }

            _placesDataSource.AllGroups.Add(group);
            return group;
        }
    }
}
